/* -------------------------------------------------------------------------
 *
 * contrib/sepgsql/hooks.c
 *
 * PostgreSQL 中钩子的入口点，并调度回调。
 *
 * Copyright (c) 2010-2022, PostgreSQL Global Development Group
 *
 * -------------------------------------------------------------------------
 */
#include "postgres.h"

#include "catalog/dependency.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_class.h"
#include "catalog/pg_database.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_proc.h"
#include "commands/seclabel.h"
#include "executor/executor.h"
#include "fmgr.h"
#include "miscadmin.h"
#include "sepgsql.h"
#include "tcop/utility.h"
#include "utils/guc.h"
#include "utils/queryenvironment.h"

PG_MODULE_MAGIC;

/*
 * 声明
 */
void		_PG_init(void);

/*
 * 保存的钩子条目（如果堆叠）
 */
static object_access_hook_type next_object_access_hook = NULL;
static ExecutorCheckPerms_hook_type next_exec_check_perms_hook = NULL;
static ProcessUtility_hook_type next_ProcessUtility_hook = NULL;

/*
 * DDL 命令的上下文信息
 */
typedef struct
{
	NodeTag		cmdtype;

	/*
	 * 用户在 CREATE DATABASE 命令中给出的模板数据库的名称。
	 * 在其他地方（包括默认情况）为 NULL。
	 */
	const char *createdb_dtemplate;
}			sepgsql_context_info_t;

static sepgsql_context_info_t sepgsql_context_info;

/*
 * GUC: sepgsql.permissive = (开启|关闭)
 */
static bool sepgsql_permissive;

bool sepgsql_get_permissive(void)
{
	return sepgsql_permissive;
}

/*
 * GUC: sepgsql.debug_audit = (开启|关闭)
 */
static bool sepgsql_debug_audit;

bool sepgsql_get_debug_audit(void)
{
	return sepgsql_debug_audit;
}

/*
 * sepgsql_object_access
 *
 * 对对象访问钩子的入口点。此例程作为
 * 基于访问类型和对象类的调用调度程序。
 */
static void fc_sepgsql_object_access(ObjectAccessType fc_access,
					  Oid fc_classId,
					  Oid fc_objectId,
					  int fc_subId,
					  void *fc_arg)
{
	if (next_object_access_hook)
		(*next_object_access_hook) (fc_access, fc_classId, fc_objectId, fc_subId, fc_arg);

	switch (fc_access)
	{
		case OAT_POST_CREATE:
			{
				ObjectAccessPostCreate *fc_pc_arg = fc_arg;
				bool		fc_is_internal;

				fc_is_internal = fc_pc_arg ? fc_pc_arg->is_internal : false;

				switch (fc_classId)
				{
					case DatabaseRelationId:
						Assert(!fc_is_internal);
						sepgsql_database_post_create(fc_objectId,
													 sepgsql_context_info.createdb_dtemplate);
						break;

					case NamespaceRelationId:
						Assert(!fc_is_internal);
						sepgsql_schema_post_create(fc_objectId);
						break;

					case RelationRelationId:
						if (fc_subId == 0)
						{
							/*
							 * 我们希望在创建新关系时应用权限
							 * 检查的情况对应于直接用户调用。对于内部使用，
							 * 即创建 toast 表、索引重建
							 * 或 ALTER TABLE 命令，我们既不需要
							 * 分配安全标签也不需要权限
							 * 检查。
							 */
							if (fc_is_internal)
								break;

							sepgsql_relation_post_create(fc_objectId);
						}
						else
							sepgsql_attribute_post_create(fc_objectId, fc_subId);
						break;

					case ProcedureRelationId:
						Assert(!fc_is_internal);
						sepgsql_proc_post_create(fc_objectId);
						break;

					default:
						/* 忽略不支持的对象类 */
						break;
				}
			}
			break;

		case OAT_DROP:
			{
				ObjectAccessDrop *fc_drop_arg = (ObjectAccessDrop *) fc_arg;

				/*
				 * 由于内部清理，不需要在对象删除时应用权限检查；
				 * 例如，在会话关闭时移除临时数据库
				 * 对象。
				 */
				if ((fc_drop_arg->dropflags & PERFORM_DELETION_INTERNAL) != 0)
					break;

				switch (fc_classId)
				{
					case DatabaseRelationId:
						sepgsql_database_drop(fc_objectId);
						break;

					case NamespaceRelationId:
						sepgsql_schema_drop(fc_objectId);
						break;

					case RelationRelationId:
						if (fc_subId == 0)
							sepgsql_relation_drop(fc_objectId);
						else
							sepgsql_attribute_drop(fc_objectId, fc_subId);
						break;

					case ProcedureRelationId:
						sepgsql_proc_drop(fc_objectId);
						break;

					default:
						/* 忽略不支持的对象类 */
						break;
				}
			}
			break;

		case OAT_TRUNCATE:
			{
				switch (fc_classId)
				{
					case RelationRelationId:
						sepgsql_relation_truncate(fc_objectId);
						break;
					default:
						/* 忽略不支持的对象类 */
						break;
				}
			}
			break;

		case OAT_POST_ALTER:
			{
				ObjectAccessPostAlter *fc_pa_arg = fc_arg;
				bool		fc_is_internal = fc_pa_arg->is_internal;

				switch (fc_classId)
				{
					case DatabaseRelationId:
						Assert(!fc_is_internal);
						sepgsql_database_setattr(fc_objectId);
						break;

					case NamespaceRelationId:
						Assert(!fc_is_internal);
						sepgsql_schema_setattr(fc_objectId);
						break;

					case RelationRelationId:
						if (fc_subId == 0)
						{
							/*
							 * 一种不想应用权限检查的情况是关系
							 * 在没有用户意图的情况下被内部修改。
							 * 例如，不需要检查在表重写结束时
							 * 被重命名的 toast 表/索引。
							 */
							if (fc_is_internal)
								break;

							sepgsql_relation_setattr(fc_objectId);
						}
						else
							sepgsql_attribute_setattr(fc_objectId, fc_subId);
						break;

					case ProcedureRelationId:
						Assert(!fc_is_internal);
						sepgsql_proc_setattr(fc_objectId);
						break;

					default:
						/* 忽略不支持的对象类 */
						break;
				}
			}
			break;

		case OAT_NAMESPACE_SEARCH:
			{
				ObjectAccessNamespaceSearch *fc_ns_arg = fc_arg;

				/*
				 * 如果堆叠的扩展已经决定不允许用户
				 * 搜索此模式，我们就遵循该决定。
				 */
				if (!fc_ns_arg->result)
					break;

				Assert(fc_classId == NamespaceRelationId);
				Assert(fc_ns_arg->result);
				fc_ns_arg->result
					= sepgsql_schema_search(fc_objectId,
											fc_ns_arg->ereport_on_violation);
			}
			break;

		case OAT_FUNCTION_EXECUTE:
			{
				Assert(fc_classId == ProcedureRelationId);
				sepgsql_proc_execute(fc_objectId);
			}
			break;

		default:
			elog(ERROR, "unexpected object access type: %d", (int) fc_access);
			break;
	}
}

/*
 * sepgsql_exec_check_perms
 *
 * DML 权限的入口点
 */
static bool fc_sepgsql_exec_check_perms(List *fc_rangeTabls, bool fc_abort)
{
	/*
	 * 如果安全提供程序正在堆叠，且其中一个返回了 'false'
	 * 至少，我们就不需要再检查了。
	 */
	if (next_exec_check_perms_hook &&
		!(*next_exec_check_perms_hook) (fc_rangeTabls, fc_abort))
		return false;

	if (!sepgsql_dml_privileges(fc_rangeTabls, fc_abort))
		return false;

	return true;
}

/*
 * sepgsql_utility_command
 *
 * 它尝试对实用程序命令进行粗粒度控制；
 * 如果恶意用户使用其中一些命令，可能会破坏整个系统。
 */
static void fc_sepgsql_utility_command(PlannedStmt *fc_pstmt,
						const char *fc_queryString,
						bool fc_readOnlyTree,
						ProcessUtilityContext fc_context,
						ParamListInfo fc_params,
						QueryEnvironment *fc_queryEnv,
						DestReceiver *fc_dest,
						QueryCompletion *fc_qc)
{
	Node	   *fc_parsetree = fc_pstmt->utilityStmt;
	sepgsql_context_info_t fc_saved_context_info = sepgsql_context_info;
	ListCell   *fc_cell;

	PG_TRY();
	{
		/*
		 * 检查命令标签以避免恶意操作，并保存当前上下文信息
		 * 以决定我们是否在这里应用权限检查。
		 */
		sepgsql_context_info.cmdtype = nodeTag(fc_parsetree);

		switch (nodeTag(fc_parsetree))
		{
			case T_CreatedbStmt:

				/*
				 * 我们希望引用源数据库的名称，但它
				 * 不出现在系统目录中。因此，我们在这里保存它。
				 */
				foreach(fc_cell, ((CreatedbStmt *) fc_parsetree)->options)
				{
					DefElem    *fc_defel = (DefElem *) lfirst(fc_cell);

					if (strcmp(fc_defel->defname, "template") == 0)
					{
						sepgsql_context_info.createdb_dtemplate
							= strVal(fc_defel->arg);
						break;
					}
				}
				break;

			case T_LoadStmt:

				/*
				 * 在强制模式下，我们总体上拒绝 LOAD 命令，
				 * 因为二进制模块可以任意覆盖钩子。
				 */
				if (sepgsql_getenforce())
				{
					ereport(ERROR,
							(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
							 errmsg("SELinux: LOAD is not permitted")));
				}
				break;
			default:

				/*
				 * 目前我们不检查其他实用程序命令，
				 * 因为这需要更多详细信息以做出访问
				 * 控制决策，但我们不想单独进行两次解析
				 * 和分析例程。
				 */
				break;
		}

		if (next_ProcessUtility_hook)
			(*next_ProcessUtility_hook) (fc_pstmt, fc_queryString, fc_readOnlyTree,
										 fc_context, fc_params, fc_queryEnv,
										 fc_dest, fc_qc);
		else
			standard_ProcessUtility(fc_pstmt, fc_queryString, fc_readOnlyTree,
									fc_context, fc_params, fc_queryEnv,
									fc_dest, fc_qc);
	}
	PG_FINALLY();
	{
		sepgsql_context_info = fc_saved_context_info;
	}
	PG_END_TRY();
}

/*
 * 模块加载/卸载回调
 */
void _PG_init(void)
{
	/*
	 * 我们仅允许在单用户模式或
	 * shared_preload_libraries 设置下加载 SE-PostgreSQL 模块。
	 */
	if (IsUnderPostmaster)
		ereport(ERROR,
				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
				 errmsg("sepgsql must be loaded via shared_preload_libraries")));

	/*
	 * 检查平台上 SELinux 的可用性。如果禁用，我们无法
	 * 激活任何 SE-PostgreSQL 功能，并且必须跳过初始化的其余部分。
	 */
	if (is_selinux_enabled() < 1)
	{
		sepgsql_set_mode(SEPGSQL_MODE_DISABLED);
		return;
	}

	/*
	 * sepgsql.permissive = (on|off)
	 *
	 * 该变量控制用户会话中 SE-PostgreSQL 的执行模式。
	 */
	DefineCustomBoolVariable("sepgsql.permissive",
							 "Turn on/off permissive mode in SE-PostgreSQL",
							 NULL,
							 &sepgsql_permissive,
							 false,
							 PGC_SIGHUP,
							 GUC_NOT_IN_SAMPLE,
							 NULL,
							 NULL,
							 NULL);

	/*
	 * sepgsql.debug_audit = (on|off)
	 *
	 * 该变量允许用户开启/关闭对访问控制决策的审计日志，独立于
	 * 安全策略中的 auditallow/auditdeny 设置。我们打算将此选项用于调试目的。
	 */
	DefineCustomBoolVariable("sepgsql.debug_audit",
							 "Turn on/off debug audit messages",
							 NULL,
							 &sepgsql_debug_audit,
							 false,
							 PGC_USERSET,
							 GUC_NOT_IN_SAMPLE,
							 NULL,
							 NULL,
							 NULL);

	MarkGUCPrefixReserved("sepgsql");

	/* 初始化用户空间访问向量缓存 */
	sepgsql_avc_init();

	/* 初始化客户端的安全标签及相关内容 */
	sepgsql_init_client_label();

	/* 安全标签提供者钩子 */
	register_label_provider(SEPGSQL_LABEL_TAG,
							sepgsql_object_relabel);

	/* 对象访问钩子 */
	next_object_access_hook = object_access_hook;
	object_access_hook = fc_sepgsql_object_access;

	/* DML 权限检查 */
	next_exec_check_perms_hook = ExecutorCheckPerms_hook;
	ExecutorCheckPerms_hook = fc_sepgsql_exec_check_perms;

	/* 处理实用工具钩子 */
	next_ProcessUtility_hook = ProcessUtility_hook;
	ProcessUtility_hook = fc_sepgsql_utility_command;

	/* 初始化上下文信息 */
	memset(&sepgsql_context_info, 0, sizeof(sepgsql_context_info));
}
